﻿using Bookify.Application.Abstractions.Clock;
using Bookify.Application.Exceptions;
using Bookify.Domain.Abstractions;
using Bookify.Infrastructure.Outbox;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;

namespace Bookify.Infrastructure
{
    public sealed class ApplicationDbContext : DbContext, IUnitOfWork
    {
        private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
        {
            TypeNameHandling = TypeNameHandling.All
        };

        private readonly IDateTimeProvider _dateTimeProvider;
        
        public ApplicationDbContext(DbContextOptions options, IDateTimeProvider dateTimeProvider) 
            : base(options)
        {            
            _dateTimeProvider = dateTimeProvider;
        }

        public async Task<int> SaveChangeAsync(CancellationToken cancellationToken = default)
        {
            try
            {
                AddDomainEventsAsOutboxMessage();

                var result = await base.SaveChangesAsync(cancellationToken);                

                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                throw new ConcurrencyException("发生并发异常",ex);
            }            
        }

        private void AddDomainEventsAsOutboxMessage()
        {
            var outboxMessages = ChangeTracker.Entries<Entity>()
                .Select(e => e.Entity)
                .SelectMany(entity =>
                {
                    var domainEvents = entity.GetDomainEvents();
                    entity.ClearDomainEvents();
                    return domainEvents;
                })
                .Select(domainEvent => new OutboxMessage(
                    Guid.NewGuid(), 
                    _dateTimeProvider.UtcNow, 
                    domainEvent.GetType().Name, 
                    JsonConvert.SerializeObject(domainEvent, _jsonSerializerSettings))
                )
                .ToList();

            AddRange(outboxMessages);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
            base.OnModelCreating(modelBuilder);
        }
    }
}
